<script lang="ts">
type DirectionType = 'left' | 'right';

type ValueType = string | number;

type BaseItem = {
  id: ValueType;
  title: string;
  children?: BaseItem[];
  disabled?: boolean;
};

type RightItem = { id: ValueType; title: string };

// 克隆
function clone<T>(value: T) {
  return JSON.parse(JSON.stringify(value));
}
</script>

<script lang="ts" setup generic="T extends BaseItem">
import { ref, computed } from 'vue';

import { ArrowLeft, ArrowRight } from '@element-plus/icons-vue';

const props = withDefaults(
  defineProps<{ modelValue: ValueType[]; height: number; data: T[] }>(),
  {
    height: 250,
    data: () => [],
  }
);

const emit = defineEmits<{
  (e: 'update:modelValue', value: ValueType[]): void;
}>();

const elTreeRef = ref();

const rightData = ref<RightItem[]>([]);

const leftChecked = ref<T[]>([]);

const rightChecked = ref<ValueType[]>([]);

const selected = computed(() => {
  return rightData.value.map((item) => item.id);
});

/**
 * 选中
 */
const handleCheck = (value: any) => {
  console.log();
  console.log('---------------------');
  console.log('check => ', value);
  console.log('---------------------');
  console.log();
};

/**
 * 改变
 */
const handleChange = (direction: DirectionType) => {
  console.log();
  console.log('---------------------');
  console.log('elTreeRef => ', elTreeRef.value);
  console.log('---------------------');
  console.log();

  if (direction === 'right') {
    leftChecked.value = elTreeRef.value.getCheckedNodes();

    console.log();
    console.log('---------------------');
    console.log('right => ', rightData.value);
    console.log('---------------------');
    console.log();

    const nodes = (leftChecked.value as T[]).reduce<RightItem[]>(
      (acc, curr) => {
        if (
          curr.children &&
          curr.children.length === 0 &&
          !selected.value.includes(curr.id)
        ) {
          curr.disabled = true;
          acc.push({ id: curr.id, title: curr.title });
        }
        return acc;
      },
      []
    );

    rightData.value = [...rightData.value, ...nodes];
  }

  if (direction === 'left') {
    leftChecked.value.forEach((item) => {
      if (rightChecked.value.includes(item.id)) {
        item.disabled = false;
        setTimeout(() => elTreeRef.value.setChecked(item.id, false), 0);
      }
    });

    rightData.value = rightData.value.filter(
      (item) => !rightChecked.value.includes(item.id)
    );

    rightChecked.value = [];
  }

  emit('update:modelValue', clone<ValueType[]>(selected.value));
};
</script>

<template>
  <div class="component" :style="{ height: props.height + 'px' }">
    <div class="component__block component__status--style1">
      <div class="component__row component__status--style2">待选</div>

      <div class="component__block component__status--style3">
        <el-tree ref="elTreeRef" :data="props.data" v-bind="$attrs"></el-tree>
      </div>
    </div>

    <div class="component__block component__status--style4">
      <div
        class="component__button--right"
        @click="() => handleChange('right')"
      >
        <el-icon><ArrowRight /></el-icon>
      </div>

      <div class="component__button--left" @click="() => handleChange('left')">
        <el-icon><ArrowLeft /></el-icon>
      </div>
    </div>

    <div class="component__block component__status--style1">
      <div class="component__row component__status--style2">已选</div>

      <div class="component__block component__status--style3">
        <el-checkbox-group
          class="component__status--checkbox-group"
          v-model="rightChecked"
        >
          <el-checkbox
            v-for="item in rightData"
            :key="item.id"
            :label="item.id"
            >{{ item.title }}</el-checkbox
          >
        </el-checkbox-group>
      </div>
    </div>
  </div>
</template>

<style lang="scss" scoped>
.component {
  display: grid;
  grid-template-columns: 1fr 100px 1fr;

  width: 100%;

  &__button {
    &--left,
    &--right {
      display: flex;
      justify-content: center;
      align-items: center;

      width: 40px;
      height: 40px;
      border-radius: 50%;
      box-shadow: 0px 3px 6px 1px rgba(0, 0, 0, 0.16);
    }

    &--left:active,
    &--right:active {
      background-color: #409eff;
    }
  }

  &__status {
    &--style1 {
      display: flex;
      flex-direction: column;

      border: 1px solid #dcdfe6;
      border-radius: 8px;
      overflow: hidden;
    }

    &--style2 {
      padding: 4px 20px 0;
      height: 40px;
      border-bottom: 1px solid #dcdfe6;
    }

    &--style3 {
      padding: 10px 5px;
      flex: 1;
      overflow-y: auto;
    }

    &--style4 {
      display: flex;
      flex-direction: column;
      justify-content: center;
      align-items: center;
    }

    &--style4 > * + * {
      margin-top: 30px;
    }

    &--checkbox-group {
      display: flex;
      flex-direction: column;
      padding-left: 10px;
    }
  }
}
</style>
